﻿using System;
using Cpuss.Properties;

namespace Cpuss
{
    /// <summary>
    /// Process.
    /// </summary>
    public sealed class Process
    {
        private readonly int _arrivalTime;
        private readonly int _burstTime;
        private readonly Pairs<int, int> _cpuActivity;
        private readonly int _id;
        private ProcessRunData _data;

        /// <summary>
        /// Initializes a new instance of <see cref="Process"/>.
        /// </summary>
        /// <param name="id">Id of process.</param>
        /// <param name="burstTime">Burst time of the process.</param>
        /// <param name="arrivalTime">Arrival time of the process.</param>
        /// <exception cref="ArgumentException"><strong>id</strong> is less than <strong>0</strong>.</exception>
        /// <exception cref="ArgumentException"><strong>burstTime</strong> is less than <strong>1</strong>.</exception>
        /// <exception cref="ArgumentException"><strong>arrivalTime</strong> is less than <strong>0</strong>.</exception>
        public Process(int id, int burstTime, int arrivalTime)
        {
            if (id < 0)
            {
                throw new ArgumentException(Resources.ProcessIdLessThanZero);
            }
            else if (burstTime < 1)
            {
                throw new ArgumentException(Resources.ProcessBurstTimeLessThanOne);
            }
            else if (arrivalTime < 0)
            {
                throw new ArgumentException(Resources.ArrivalTimeLessThanZero);
            }

            _id = id;
            _burstTime = burstTime;
            _arrivalTime = arrivalTime;
            _cpuActivity = new Pairs<int, int>();
            Priority = Priority.Medium;
        }

        /// <summary>
        /// Gets or sets the data associated with the process during simulation. 
        /// </summary>
        public ProcessRunData Data
        {
            get { return _data; }
            internal set
            {
                if (value.UtilizedCpuTime != _data.UtilizedCpuTime)
                {
                    CheckState(value);
                }
                _data = value;
            }
        }

        /// <summary>
        /// Gets the Id of the process (PID).
        /// </summary>
        public int Id
        {
            get { return _id; }
        }

        /// <summary>
        /// Gets the burst time of the process in nanoseconds (ns).
        /// </summary>
        public int BurstTime
        {
            get { return _burstTime; }
        }

        /// <summary>
        /// Gets the arrival time of the process in nanoseconds (ns).
        /// </summary>
        public int ArrivalTime
        {
            get { return _arrivalTime; }
        }

        /// <summary>
        /// Gets the CPU activity of the process.
        /// </summary>
        public Pairs<int, int> CpuActivity
        {
            get { return _cpuActivity; }
        }

        /// <summary>
        /// Gets or sets the start time of the process in nanoseconds (ns).
        /// </summary>
        public int StartTime { get; set; }

        /// <summary>
        /// Gets or sets the completion time of the process in nanoseconds (ns).
        /// </summary>
        public int CompletionTime { get; set; }

        /// <summary>
        /// Gets or sets the <see cref="Priority"/> of the process.
        /// </summary>
        public Priority Priority { get; set; }

        /// <summary>
        /// Gets the wait time of the <see cref="Process"/>.
        /// </summary>
        public int WaitTime { get; set; }

        /// <summary>
        /// Gets or sets the turnaround time of the process.
        /// </summary>
        public int TurnaroundTime { get; set; }

        /// <summary>
        /// Gets or sets the resposen time of the process.
        /// </summary>
        public int ResponseTime { get; set; }

        /// <summary>
        /// Occurs when the process utilizes the CPU for the first time.
        /// </summary>
        public event EventHandler<ProcessStartedEventArgs> Started;

        /// <summary>
        /// Occurs when the process has utilized enough CPU time to satisfy it's burst time.
        /// </summary>
        public event EventHandler<ProcessCompletedEventArgs> Completed;

        /// <summary>
        /// Raises the <see cref="Started"/> event.
        /// </summary>
        /// <param name="e">Data associated with the process when it has started.</param>
        private void OnStarted(ProcessStartedEventArgs e)
        {
            EventHandler<ProcessStartedEventArgs> temp = Started;
            if (temp != null)
            {
                temp(this, e);
            }
        }

        /// <summary>
        /// Raises the <see cref="Completed"/> event.
        /// </summary>
        /// <param name="e">Data associated with the process upon its completion.</param>
        private void OnCompleted(ProcessCompletedEventArgs e)
        {
            EventHandler<ProcessCompletedEventArgs> temp = Completed;
            if (temp != null)
            {
                temp(this, e);
            }
        }

        /// <summary>
        /// Check's the state of the process, e.g. if the process has just started or utilized enough CPU time to 
        /// satisfy its burst time.
        /// </summary>
        /// <param name="data">Data associated with this process during the simulation.</param>
        private void CheckState(ProcessRunData data)
        {
            // check to see if the process hasn't utilized the CPU before
            if (_data.UtilizedCpuTime == 0)
            {
                StartTime = data.RunnerTime - 1;
                OnStarted(new ProcessStartedEventArgs(Id, BurstTime, ArrivalTime, Priority, StartTime));
                ResponseTime = StartTime - _arrivalTime;
            }
            // check to see if the process has utilized enough CPU time to voluntarily release the CPU
            if (data.UtilizedCpuTime == BurstTime)
            {
                CompletionTime = data.RunnerTime;
                OnCompleted(new ProcessCompletedEventArgs(Id, BurstTime, ArrivalTime, Priority, StartTime,
                                                          CompletionTime));
                TurnaroundTime = CompletionTime - _arrivalTime;
            }
        }

        /// <summary>
        /// Checks whether a given process has utilized enough CPU time to satisfy it's overall burst time.
        /// </summary>
        /// <param name="process">Process to check is complete.</param>
        /// <returns>True if the process has completed; otherwise false.</returns>
        public static bool IsComplete(Process process)
        {
            return process.BurstTime - process.Data.UtilizedCpuTime == 0 ? true : false;
        }
    }
}